package corejava.corejava2.ch02.p02;

import java.awt.*;
import java.beans.*;
import java.io.*;
import java.lang.reflect.*;
import javax.swing.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;

/**
 * 使用GridBagPane类来完成设定网格组布局时的所有(无聊的)工作
 * This panel uses an XML file to describe its components and their grid bag layout positions.
 * 
 * @version 1.10 2004-09-04
 * @author Cay Horstmann
 */
@SuppressWarnings("serial")
public class GridBagPane extends JPanel {
    /**
     * Constructs a grid bag pane.
     * 
     * @param filename
     *            the name of the XML file that describes the pane's components and their
     *            positions
     */
    public GridBagPane(String filename) {
        setLayout(new GridBagLayout());
        constraints = new GridBagConstraints();

        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setValidating(true);   // A 首先要通知文档生成工厂打开验证特性
            // 这样该文档生成器就会根据DTD来验证XML文件的正确性

            if (filename.contains("-schema"))  // 解析带有Schema的XML文件和解析带有DTD的文件类似,但是有以下几点差别
            {
                factory.setNamespaceAware(true); // 1 必须打开对命名空间的支持
                // 2 再用以下"魔咒"来处理schema工厂
                final String JAXP_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
                final String W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema";
                factory.setAttribute(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA);
            }

            factory.setIgnoringElementContentWhitespace(true); // B true工厂生成的解析器会忽略元素间的空白字符

            DocumentBuilder builder = factory.newDocumentBuilder();
            Document doc = builder.parse(new File(filename));

            if (filename.contains("-schema"))  // 3 由于Schema解析器不会丢弃空白元素(即使在B中设定了)
            {  // ,所以要用以下自定义的方法来去掉空白字符元素
                int count = removeElementContentWhitespace(doc.getDocumentElement());
                System.out.println(count + " whitespace nodes removed.");
            }

            parseGridbag(doc.getDocumentElement());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 删除所有的空白的字符的元素
     * Removes all (heuristically determined) element content whitespace nodes
     * 
     * @param e
     *            the root element
     * @return the number of whitespace nodes that were removed.
     */
    private int removeElementContentWhitespace(Element e) {
        NodeList children = e.getChildNodes();
        int count = 0;
        boolean allTextChildrenAreWhiteSpace = true;
        int elements = 0;
        for (int i = 0; i < children.getLength() && allTextChildrenAreWhiteSpace; i++) {
            Node child = children.item(i);
            if (child instanceof Text && ((Text) child).getData().trim().length() > 0)
                allTextChildrenAreWhiteSpace = false;
            else if (child instanceof Element) {
                elements++;
                // 这里递归的调用了函数本身
                count += removeElementContentWhitespace((Element) child);
            }
        }
        if (elements > 0 && allTextChildrenAreWhiteSpace) // heuristics for element content
        {
            for (int i = children.getLength() - 1; i >= 0; i--) {
                Node child = children.item(i);
                if (child instanceof Text) {
                    e.removeChild(child);
                    count++;
                }
            }
        }
        return count;
    }

    /**
     * Gets a component with a given name
     * 
     * @param name
     *            a component name
     * @return the component with the given name, or null if no component in this grid bag pane has
     *         the given name
     */
    public Component get(String name) {
        Component[] components = getComponents();
        for (int i = 0; i < components.length; i++) {
            if (components[i].getName().equals(name))
                return components[i];
        }
        return null;
    }

    /**
     * Parses a gridbag element.
     * 
     * @param e
     *            a gridbag element
     */
    private void parseGridbag(Element e) {
        NodeList rows = e.getChildNodes();
        for (int i = 0; i < rows.getLength(); i++) {
            Element row = (Element) rows.item(i);
            NodeList cells = row.getChildNodes();
            for (int j = 0; j < cells.getLength(); j++) {
                Element cell = (Element) cells.item(j);
                parseCell(cell, i, j);
            }
        }
    }

    /**
     * Parses a cell element.
     * 
     * @param e
     *            a cell element
     * @param r
     *            the row of the cell
     * @param c
     *            the column of the cell
     */
    private void parseCell(Element e, int r, int c) {
        // get attributes

        String value = e.getAttribute("gridx");
        if (value.length() == 0) // use default
        {
            if (c == 0)
                constraints.gridx = 0;
            else
                constraints.gridx += constraints.gridwidth;
        } else
            constraints.gridx = Integer.parseInt(value);

        value = e.getAttribute("gridy");
        if (value.length() == 0) // use default
            constraints.gridy = r;
        else
            constraints.gridy = Integer.parseInt(value);

        constraints.gridwidth = Integer.parseInt(e.getAttribute("gridwidth"));
        constraints.gridheight = Integer.parseInt(e.getAttribute("gridheight"));
        constraints.weightx = Integer.parseInt(e.getAttribute("weightx"));
        constraints.weighty = Integer.parseInt(e.getAttribute("weighty"));
        constraints.ipadx = Integer.parseInt(e.getAttribute("ipadx"));
        constraints.ipady = Integer.parseInt(e.getAttribute("ipady"));

        // use reflection to get integer values of static fields
        Class<GridBagConstraints> cl = GridBagConstraints.class;

        try {   // 在元素中获得属性,在用反射机制设置constraints
            String name = e.getAttribute("fill");
            Field f = cl.getField(name);
            constraints.fill = f.getInt(cl);

            name = e.getAttribute("anchor");
            f = cl.getField(name);
            constraints.anchor = f.getInt(cl);
        } catch (Exception ex) // the reflection methods can throw various exceptions
        {
            ex.printStackTrace();
        }

        Component comp = (Component) parseBean((Element) e.getFirstChild());
        add(comp, constraints);
    }

    /**
     * Parses a bean element.
     * 
     * @param e
     *            a bean element
     */
    private Object parseBean(Element e) {
        try {
            NodeList children = e.getChildNodes();
            // 读第一个子元素(类名)
            Element classElement = (Element) children.item(0);
            String className = ((Text) classElement.getFirstChild()).getData();

            Class<?> cl = Class.forName(className);

            Object obj = cl.newInstance();

            if (obj instanceof Component)
                ((Component) obj).setName(e.getAttribute("id"));

            for (int i = 1; i < children.getLength(); i++) { // 第二个到最后一个子元素
                Node propertyElement = children.item(i);
                Element nameElement = (Element) propertyElement.getFirstChild();
                String propertyName = ((Text) nameElement.getFirstChild()).getData(); 

                Element valueElement = (Element) propertyElement.getLastChild();
                Object value = parseValue(valueElement);
                BeanInfo beanInfo = Introspector.getBeanInfo(cl);
                PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
                boolean done = false;
                for (int j = 0; !done && j < descriptors.length; j++) {
                    if (descriptors[j].getName().equals(propertyName)) {
                        descriptors[j].getWriteMethod().invoke(obj, value);
                        done = true;
                    }
                }

            }
            return obj;
        } catch (Exception ex) // the reflection methods can throw various exceptions
        {
            ex.printStackTrace();
            return null;
        }
    }

    /**
     * Parses a value element.
     * 
     * @param e
     *            a value element
     */
    private Object parseValue(Element e) {
        Element child = (Element) e.getFirstChild();
        if (child.getTagName().equals("bean"))
            return parseBean(child);
        String text = ((Text) child.getFirstChild()).getData();
        if (child.getTagName().equals("int"))
            return new Integer(text);
        else if (child.getTagName().equals("boolean"))
            return new Boolean(text);
        else if (child.getTagName().equals("string"))
            return text;
        else
            return null;
    }

    private GridBagConstraints constraints;
}
